using UnityEngine;
using System.Collections;
using System.Collections.Generic;

/*
 * Interpolation utility functions: easing, bezier, and catmull-rom.
 * Consider using Unity's Animation curve editor and AnimationCurve class
 * before scripting the desired behaviour using this utility.
 *
 * Interpolation functionality available at different levels of abstraction.
 * Low level access via individual easing functions (ex. EaseInOutCirc),
 * Bezier(), and CatmullRom(). High level access using sequence generators,
 * NewEase(), NewBezier(), and NewCatmullRom().
 *
 * Sequence generators are typically used as follows:
 *
 * var sequence = gkInterpolate.New[Ease|Bezier|CatmulRom](configuration);
 * for (var newPoint in sequence) {
 *   transform.position = newPoint;
 *   yield;
 * }
 *
 * Or:
 *
 * var sequence = gkInterpolate.New[Ease|Bezier|CatmulRom](configuration);
 * function Update() {
 *   if (sequence.MoveNext()) {
 *     transform.position = sequence.Current;
 *   }
 * }
 *
 * The low level functions work similarly to Unity's built in Lerp and it is
 * up to you to track and pass in elapsedTime and duration on every call. The
 * functions take this form (or the logical equivalent for Bezier() and
 * CatmullRom()).
 *
 * transform.position = ease(start, distance, elapsedTime, duration);
 *
 * For convenience in configuration you can use the Ease(EaseType) function to
 * look up a concrete easing function:
 *
 * var easeType : EaseType; // set using Unity's property inspector
 * var ease : EaseFunction; // easing of a particular EaseType
 * function Awake() {
 *   ease = gkInterpolate.Ease(easeType);
 * }
 *
 * @author Fernando Zapata (fernando@cpudreams.com)
*/

/// Javascript to C# conversion

/// \class  gkInterpolate
/// \brief  Interpolation utility function
public class gkInterpolate 
{
	
	public delegate float EaseFunction(float start, float distance, float elapsedTime, float duration);
	public delegate Vector3 TransformToPositionFunction(Transform t);
	public delegate Vector3 PositionToPositionFunction(Vector3 v3);
	
	/**
	 * Different methods of easing interpolation.
	 */
	public enum EaseType {
	  Linear,
	  EaseInQuad,
	  EaseOutQuad,
	  EaseInOutQuad,
	  EaseInCubic,
	  EaseOutCubic,
	  EaseInOutCubic,
	  EaseInQuart,
	  EaseOutQuart,
	  EaseInOutQuart,
	  EaseInQuint,
	  EaseOutQuint,
	  EaseInOutQuint,
	  EaseInSine,
	  EaseOutSine,
	  EaseInOutSine,
	  EaseInExpo,
	  EaseOutExpo,
	  EaseInOutExpo,
	  EaseInCirc,
	  EaseOutCirc,
	  EaseInOutCirc
	}
	
	/**
	 * Returns sequence generator from start to end over duration using the
	 * given easing function. The sequence is generated as it is accessed
	 * using the Time.deltaTime to calculate the portion of duration that has
	 * elapsed.
	 */
	public static IEnumerator NewEase(EaseFunction ease, Vector3 start, Vector3 end, float duration) 
	{
		IEnumerator timer = NewTimer(duration);
		return NewEase(ease, start, end, duration, timer);
	}
	
	/**
	 * Instead of easing based on time, generate n interpolated points (slices)
	 * between the start and end positions.
	 */
	public static IEnumerator NewEase(EaseFunction ease, Vector3 start, Vector3 end, int slices) 
	{
		IEnumerator counter = NewCounter(0, slices + 1, 1);
		return NewEase(ease, start, end, slices + 1, counter);
	}
	
	/**
	 * Generic easing sequence generator used to implement the time and
	 * slice variants. Normally you would not use this function directly.
	 */
	public static IEnumerator NewEase(EaseFunction ease, Vector3 start, Vector3 end, float total, IEnumerator driver)
	{
		Vector3 distance = end - start;
		while(driver.MoveNext()) 
		{
			yield return Ease(ease, start, distance, (float)driver.Current, total);
		}
	}
	
	/**
	 * Vector3 interpolation using given easing method. Easing is done independently
	 * on all three vector axis.
	 */
	public static Vector3 Ease(EaseFunction ease, Vector3 start, Vector3 distance, float elapsedTime, float duration)
	{
		start.x = ease(start.x, distance.x, elapsedTime, duration);
		start.y = ease(start.y, distance.y, elapsedTime, duration);
		start.z = ease(start.z, distance.z, elapsedTime, duration);
		return start;
	}
	
	/**
	 * Returns the static method that implements the given easing type for scalars.
	 * Use this method to easily switch between easing interpolation types.
	 *
	 * All easing methods clamp elapsedTime so that it is always <= duration.
	 *
	 * var ease = gkInterpolate.Ease(EaseType.EaseInQuad);
	 * i = ease(start, distance, elapsedTime, duration);
	 */
	public static EaseFunction Ease(EaseType type)
	{
		// Source Flash easing functions:
		// http://gizma.com/easing/
		// http://www.robertpenner.com/easing/easing_demo.html
		//
		// Changed to use more friendly variable names, that follow my Lerp
		// conventions:
		// start = b (start value)
		// distance = c (change in value)
		// elapsedTime = t (current time)
		// duration = d (time duration)
		
		EaseFunction f;
			
		switch (type) 
		{
			case EaseType.Linear: f = gkInterpolate.Linear; break;
			case EaseType.EaseInQuad: f = gkInterpolate.EaseInQuad; break;
			case EaseType.EaseOutQuad: f = gkInterpolate.EaseOutQuad; break;
			case EaseType.EaseInOutQuad: f = gkInterpolate.EaseInOutQuad; break;
			case EaseType.EaseInCubic: f = gkInterpolate.EaseInCubic; break;
			case EaseType.EaseOutCubic: f = gkInterpolate.EaseOutCubic; break;
			case EaseType.EaseInOutCubic: f = gkInterpolate.EaseInOutCubic; break;
			case EaseType.EaseInQuart: f = gkInterpolate.EaseInQuart; break;
			case EaseType.EaseOutQuart: f = gkInterpolate.EaseOutQuart; break;
			case EaseType.EaseInOutQuart: f = gkInterpolate.EaseInOutQuart; break;
			case EaseType.EaseInQuint: f = gkInterpolate.EaseInQuint; break;
			case EaseType.EaseOutQuint: f = gkInterpolate.EaseOutQuint; break;
			case EaseType.EaseInOutQuint: f = gkInterpolate.EaseInOutQuint; break;
			case EaseType.EaseInSine: f = gkInterpolate.EaseInSine; break;
			case EaseType.EaseOutSine: f = gkInterpolate.EaseOutSine; break;
			case EaseType.EaseInOutSine: f = gkInterpolate.EaseInOutSine; break;
			case EaseType.EaseInExpo: f = gkInterpolate.EaseInExpo; break;
			case EaseType.EaseOutExpo: f = gkInterpolate.EaseOutExpo; break;
			case EaseType.EaseInOutExpo: f = gkInterpolate.EaseInOutExpo; break;
			case EaseType.EaseInCirc: f = gkInterpolate.EaseInCirc; break;
			case EaseType.EaseOutCirc: f = gkInterpolate.EaseOutCirc; break;
			case EaseType.EaseInOutCirc: f = gkInterpolate.EaseInOutCirc; break;
			default : f = gkInterpolate.Linear; break;
		}
		return f;
	}
	
	/**
	 * Returns sequence generator from the first node to the last node over
	 * duration time using the points in-between the first and last node
	 * as control points of a bezier curve used to generate the interpolated points
	 * in the sequence. If there are no control points (ie. only two nodes, first
	 * and last) then this behaves exactly the same as NewEase(). In other words
	 * a zero-degree bezier spline curve is just the easing method. The sequence
	 * is generated as it is accessed using the Time.deltaTime to calculate the
	 * portion of duration that has elapsed.
	 */
	public static IEnumerator NewBezier(EaseFunction ease, Transform[] nodes, float duration)
	{
		IEnumerator timer = NewTimer(duration);
			
		return NewBezier(ease, nodes, TransformDotPosition, duration, timer);
	}
	
	/**
	 * Instead of interpolating based on time, generate n interpolated points
	 * (slices) between the first and last node.
	 */
	public static IEnumerator NewBezier(EaseFunction ease, Transform[] nodes, int slices)
	{
		IEnumerator counter = NewCounter(0, slices + 1, 1);
			
		return NewBezier(ease, nodes, TransformDotPosition, slices + 1, counter);
	}
	
	/**
	 * A Vector3[] variation of the Transform[] NewBezier() function.
	 * Same functionality but using Vector3s to define bezier curve.
	 */
	public static IEnumerator NewBezier(EaseFunction ease, Vector3[] points, float duration)
	{
		IEnumerator timer = NewTimer(duration);
			
	  	return NewBezier(ease, points, Identity, duration, timer);
	}
	
	/**
	 * A Vector3[] variation of the Transform[] NewBezier() function.
	 * Same functionality but using Vector3s to define bezier curve.
	 */
	public static IEnumerator NewBezier(EaseFunction ease, Vector3[] points, int slices) 
	{
		IEnumerator counter = NewCounter(0, slices + 1, 1);
		return NewBezier(ease, points, Identity, slices + 1, counter);
	}
	
	/**
	 * Generic bezier spline sequence generator used to implement the time and
	 * slice variants. Normally you would not use this function directly.
	 */
	public static IEnumerator NewBezier(EaseFunction ease, Transform[] nodes, TransformToPositionFunction toVector3 , float maxStep, IEnumerator steps)
	{
		// need at least two nodes to spline between
		if (nodes.Length >= 2) 
		{
			// copy nodes array since Bezier is destructive
			Vector3[] points = new Vector3[nodes.Length];
			
			while(steps.MoveNext())
			{
				// re-initialize copy before each destructive call to Bezier
				for(int i = 0; i < nodes.Length; i++)
				{
					points[i] = toVector3(nodes[i]);
				}
				
				yield return Bezier(ease, points, (float)steps.Current, maxStep);
				// make sure last value is always generated
			}
		}
	}
	
	/**
	 * Generic bezier spline sequence generator used to implement the time and
	 * slice variants. Normally you would not use this function directly.
	 */
	public static IEnumerator NewBezier(EaseFunction ease, Vector3[] nodes, PositionToPositionFunction toVector3 , float maxStep, IEnumerator steps)
	{
		// need at least two nodes to spline between
		if (nodes.Length >= 2) 
		{
			// copy nodes array since Bezier is destructive
			Vector3[] points = new Vector3[nodes.Length];
			
			while(steps.MoveNext())
			{
				// re-initialize copy before each destructive call to Bezier
				for(int i = 0; i < nodes.Length; i++)
				{
					points[i] = toVector3(nodes[i]);
				}
				
				yield return Bezier(ease, points, (float)steps.Current, maxStep);
				// make sure last value is always generated
			}
		}
	}
	
	/**
	 * A Vector3 n-degree bezier spline.
	 *
	 * WARNING: The points array is modified by Bezier. See NewBezier() for a
	 * safe and user friendly alternative.
	 *
	 * You can pass zero control points, just the start and end points, for just
	 * plain easing. In other words a zero-degree bezier spline curve is just the
	 * easing method.
	 *
	 * @param points start point, n control points, end point
	 */
	public static Vector3 Bezier(EaseFunction ease, Vector3[] points, float elapsedTime, float duration)
	{
		// Reference: http://ibiblio.org/e-notes/Splines/Bezier.htm
		// gkInterpolate the n starting points to generate the next j = (n - 1) points,
		// then interpolate those n - 1 points to generate the next n - 2 points,
		// continue this until we have generated the last point (n - (n - 1)), j = 1.
		// We store the next set of output points in the same array as the
		// input points used to generate them. This works because we store the
		// result in the slot of the input point that is no longer used for this
		// iteration.
		for(int j = points.Length - 1; j > 0; j--) 
		{
			for(int i = 0; i < j; i++) 
			{
				points[i].x = ease(points[i].x, points[i + 1].x - points[i].x, elapsedTime, duration);
				points[i].y = ease(points[i].y, points[i + 1].y - points[i].y, elapsedTime, duration);
				points[i].z = ease(points[i].z, points[i + 1].z - points[i].z, elapsedTime, duration);
			}
		}
		return points[0];
	}
	
	/**
	 * Returns sequence generator from the first node, through each control point,
	 * and to the last node. N points are generated between each node (slices)
	 * using Catmull-Rom.
	 */
	public static IEnumerator NewCatmullRom(Transform[] nodes, int slices, bool loop)
	{
		return NewCatmullRom(nodes, TransformDotPosition, slices, loop);
	}
	
	/**
	 * A Vector3[] variation of the Transform[] NewCatmullRom() function.
	 * Same functionality but using Vector3s to define curve.
	 */
	public static IEnumerator NewCatmullRom(Vector3[] points, int slices, bool loop)
	{
		return NewCatmullRom(points, Identity, slices, loop);
	}
	
	/**
	 * Generic catmull-rom spline sequence generator used to implement the
	 * Vector3[] and Transform[] variants. Normally you would not use this
	 * function directly.
	 */
	public static IEnumerator NewCatmullRom(Transform[] nodes, TransformToPositionFunction toVector3, int slices, bool loop)
	{
		// need at least two nodes to spline between
		if (nodes.Length >= 2) 
		{
			// yield the first point explicitly, if looping the first point
			// will be generated again in the step for loop when interpolating
			// from last point back to the first point
			yield return toVector3(nodes[0]);
			
			int last = nodes.Length - 1;
			for(int current = 0; loop || current < last; current++) 
			{
				// wrap around when looping
				if (loop && current > last)
				{
					current = 0;
				}
					
				// handle edge cases for looping and non-looping scenarios
				// when looping we wrap around, when not looping use start for previous
				// and end for next when you at the ends of the nodes array
				int previous = (current == 0) ? ((loop) ? last : current) : current - 1;
				int start = current;
				int end = (current == last) ? ((loop) ? 0 : current) : current + 1;
				int next = (end == last) ? ((loop) ? 0 : end) : end + 1;
				
				// adding one guarantees yielding at least the end point
				int stepCount = slices + 1;
				for (int step = 1; step <= stepCount; step++) 
				{
					yield return CatmullRom(toVector3(nodes[previous]),
					                 toVector3(nodes[start]),
					                 toVector3(nodes[end]),
					                 toVector3(nodes[next]),
					                 step, stepCount);
				}
			}
		}
	}
	
	/**
	 * Generic catmull-rom spline sequence generator used to implement the
	 * Vector3[] and Transform[] variants. Normally you would not use this
	 * function directly.
	 */
	public static IEnumerator NewCatmullRom(Vector3[] nodes, PositionToPositionFunction toVector3, int slices, bool loop)
	{
		// need at least two nodes to spline between
		if (nodes.Length >= 2) 
		{
			// yield the first point explicitly, if looping the first point
			// will be generated again in the step for loop when interpolating
			// from last point back to the first point
			yield return toVector3(nodes[0]);
			
			int last = nodes.Length - 1;
			for(int current = 0; loop || current < last; current++) 
			{
				// wrap around when looping
				if (loop && current > last)
				{
					current = 0;
				}
					
				// handle edge cases for looping and non-looping scenarios
				// when looping we wrap around, when not looping use start for previous
				// and end for next when you at the ends of the nodes array
				int previous = (current == 0) ? ((loop) ? last : current) : current - 1;
				int start = current;
				int end = (current == last) ? ((loop) ? 0 : current) : current + 1;
				int next = (end == last) ? ((loop) ? 0 : end) : end + 1;
				
				// adding one guarantees yielding at least the end point
				int stepCount = slices + 1;
				for (int step = 1; step <= stepCount; step++) 
				{
					yield return CatmullRom(toVector3(nodes[previous]),
					                 toVector3(nodes[start]),
					                 toVector3(nodes[end]),
					                 toVector3(nodes[next]),
					                 step, stepCount);
				}
			}
		}
	}
	
	/**
	 * A Vector3 Catmull-Rom spline. Catmull-Rom splines are similar to bezier
	 * splines but have the useful property that the generated curve will go
	 * through each of the control points.
	 *
	 * NOTE: The NewCatmullRom() functions are an easier to use alternative to this
	 * raw Catmull-Rom implementation.
	 *
	 * @param previous the point just before the start point or the start point
	 *                 itself if no previous point is available
	 * @param start generated when elapsedTime == 0
	 * @param end generated when elapsedTime >= duration
	 * @param next the point just after the end point or the end point itself if no
	 *             next point is available
	 */
	public static Vector3 CatmullRom(Vector3 previous, Vector3 start, Vector3 end, Vector3 next, float elapsedTime, float duration) 
	{
		// References used:
		// p.266 GemsV1
		//
		// tension is often set to 0.5 but you can use any reasonable value:
		// http://www.cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf
		//
		// bias and tension controls:
		// http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/interpolation/
		
		float percentComplete = elapsedTime / duration;
		float percentCompleteSquared = percentComplete * percentComplete;
		float percentCompleteCubed = percentCompleteSquared * percentComplete;
		
		return previous * (-0.5f*percentCompleteCubed +
		                 percentCompleteSquared -
		                 0.5f*percentComplete) +
		start * (1.5f*percentCompleteCubed +
		         -2.5f*percentCompleteSquared + 1.0f) +
		end * (-1.5f*percentCompleteCubed +
		       2.0f*percentCompleteSquared +
		       0.5f*percentComplete) +
		next * (0.5f*percentCompleteCubed -
		        0.5f*percentCompleteSquared);
	}
	
	/**
	 * Sequence of eleapsedTimes until elapsedTime is >= duration.
	 *
	 * Note: elapsedTimes are calculated using the value of Time.deltatTime each
	 * time a value is requested.
	 */
	public static IEnumerator NewTimer(float duration)
	{
		float elapsedTime = 0.0f;
		while (elapsedTime < duration)
		{
			yield return elapsedTime;
			elapsedTime += Time.deltaTime;
			// make sure last value is never skipped
			if (elapsedTime >= duration)
			{
				yield return elapsedTime;
			}
		}
	}
	
	/**
	 * Generates sequence of integers from start to end (inclusive) one step
	 * at a time.
	 */
	public static IEnumerator NewCounter(int start, int end, int step)
	{
		for(int i = start; i <= end; i += step) 
		{
			yield return i;
		}
	}
	
	public static Vector3 Identity(Vector3 v)
	{
		return v;
	}
	
	public static Vector3 TransformDotPosition(Transform t) 
	{
		return t.position;
	}
	
	/**
	 * Linear interpolation (same as Mathf.Lerp)
	 */
	public static float Linear(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime to be <= duration
		if (elapsedTime > duration) 
		{
			elapsedTime = duration;
		}
		
		return distance * (elapsedTime / duration) + start;
	}
	
	/**
	 * quadratic easing in - accelerating from zero velocity
	 */
	public static float EaseInQuad(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		return distance*elapsedTime*elapsedTime + start;
	}
	
	/**
	 * quadratic easing out - decelerating to zero velocity
	 */
	public static float EaseOutQuad(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		return -distance * elapsedTime*(elapsedTime-2) + start;
	}
	
	/**
	 * quadratic easing in/out - acceleration until halfway, then deceleration
	 */
	public static float EaseInOutQuad(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
		if (elapsedTime < 1) return distance/2*elapsedTime*elapsedTime + start;
		elapsedTime--;
		return -distance/2 * (elapsedTime*(elapsedTime-2) - 1) + start;
	}
	
	/**
	 * cubic easing in - accelerating from zero velocity
	 */
	public static float EaseInCubic(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		return distance*elapsedTime*elapsedTime*elapsedTime + start;
	}
	
	/**
	 * cubic easing out - decelerating to zero velocity
	 */
	public static float EaseOutCubic(float start, float distance, float elapsedTime, float duration)
	{
		  // clamp elapsedTime so that it cannot be greater than duration
		  elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		  elapsedTime--;
		  return distance*(elapsedTime*elapsedTime*elapsedTime + 1) + start;
	}
	
	/**
	 * cubic easing in/out - acceleration until halfway, then deceleration
	 */
	public static float EaseInOutCubic(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
		if (elapsedTime < 1) return distance/2*elapsedTime*elapsedTime*elapsedTime +
		                     start;
		elapsedTime -= 2;
		return distance/2*(elapsedTime*elapsedTime*elapsedTime + 2) + start;
	}
	
	/**
	 * quartic easing in - accelerating from zero velocity
	 */
	public static float EaseInQuart(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		return distance*elapsedTime*elapsedTime*elapsedTime*elapsedTime + start;
	}
	
	/**
	 * quartic easing out - decelerating to zero velocity
	 */
	public static float EaseOutQuart(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		elapsedTime--;
		return -distance * (elapsedTime*elapsedTime*elapsedTime*elapsedTime - 1) + start;
	}
	
	/**
	 * quartic easing in/out - acceleration until halfway, then deceleration
	 */
	public static float EaseInOutQuart(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
		if (elapsedTime < 1) return distance/2*
		                     elapsedTime*elapsedTime*elapsedTime*elapsedTime +
		                     start;
		elapsedTime -= 2;
		return -distance/2 * (elapsedTime*elapsedTime*elapsedTime*elapsedTime - 2) + start;
	}
	
	
	/**
	 * quintic easing in - accelerating from zero velocity
	 */
	public static float EaseInQuint(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		return distance*elapsedTime*elapsedTime*elapsedTime*elapsedTime*elapsedTime + start;
	}
	
	/**
	 * quintic easing out - decelerating to zero velocity
	 */
	public static float EaseOutQuint(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		elapsedTime--;
		return distance * (elapsedTime * elapsedTime * elapsedTime * elapsedTime *
		                 elapsedTime + 1) + start;
	}
	
	/**
	 * quintic easing in/out - acceleration until halfway, then deceleration
	 */
	public static float EaseInOutQuint(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
		if (elapsedTime < 1) return distance/2 * elapsedTime * elapsedTime *
		                     elapsedTime * elapsedTime * elapsedTime + start;
		elapsedTime -= 2;
		return distance/2 * (elapsedTime * elapsedTime * elapsedTime * elapsedTime *
		                   elapsedTime + 2) + start;
	}
	
	/**
	 * sinusoidal easing in - accelerating from zero velocity
	 */
	public static float EaseInSine(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime to be <= duration
		if (elapsedTime > duration) { elapsedTime = duration; }
		return -distance * Mathf.Cos(elapsedTime/duration * (Mathf.PI/2)) +
		                     distance + start;
	}
	
	/**
	 * sinusoidal easing out - decelerating to zero velocity
	 */
	public static float EaseOutSine(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime to be <= duration
		if (elapsedTime > duration) { elapsedTime = duration; }
		return distance * Mathf.Sin(elapsedTime/duration * (Mathf.PI/2)) + start;
	}
	
	/**
	 * sinusoidal easing in/out - accelerating until halfway, then decelerating
	 */
	public static float EaseInOutSine(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime to be <= duration
		if (elapsedTime > duration) { elapsedTime = duration; }
		return -distance/2 * (Mathf.Cos(Mathf.PI*elapsedTime/duration) - 1) + start;
	}
	
	/**
	 * exponential easing in - accelerating from zero velocity
	 */
	public static float EaseInExpo(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime to be <= duration
		if (elapsedTime > duration) { elapsedTime = duration; }
		return distance * Mathf.Pow( 2, 10 * (elapsedTime/duration - 1) ) + start;
	}
	
	/**
	 * exponential easing out - decelerating to zero velocity
	 */
	public static float EaseOutExpo(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime to be <= duration
		if (elapsedTime > duration) { elapsedTime = duration; }
		return distance * ( -Mathf.Pow( 2, -10 * elapsedTime/duration ) + 1 ) + start;
	}
	
	/**
	 * exponential easing in/out - accelerating until halfway, then decelerating
	 */
	public static float EaseInOutExpo(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
		if (elapsedTime < 1) return distance/2 *
		                     Mathf.Pow( 2, 10 * (elapsedTime - 1) ) + start;
		elapsedTime--;
		return distance/2 * ( -Mathf.Pow( 2, -10 * elapsedTime) + 2 ) + start;
	}
	
	/**
	 * circular easing in - accelerating from zero velocity
	 */
	public static float EaseInCirc(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		return -distance * (Mathf.Sqrt(1 - elapsedTime*elapsedTime) - 1) + start;
	}
	
	/**
	 * circular easing out - decelerating to zero velocity
	 */
	public static float EaseOutCirc(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
		elapsedTime--;
		return distance * Mathf.Sqrt(1 - elapsedTime*elapsedTime) + start;
	}
	
	/**
	 * circular easing in/out - acceleration until halfway, then deceleration
	 */
	public static float EaseInOutCirc(float start, float distance, float elapsedTime, float duration)
	{
		// clamp elapsedTime so that it cannot be greater than duration
		elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
		if (elapsedTime < 1) return -distance/2 *
		                     (Mathf.Sqrt(1 - elapsedTime*elapsedTime) - 1) + start;
		elapsedTime -= 2;
		return distance/2 * (Mathf.Sqrt(1 - elapsedTime*elapsedTime) + 1) + start;
	}
}
